<template>
	<e-picker
		ref="pickerRef"
		:show="show"
		:closeOnClickOverlay="props.closeOnClickOverlay"
		:columns="columns"
		:itemHeight="props.itemHeight"
		:showToolbar="props.showToolbar"
		:visibleItemCount="props.visibleItemCount"
		:defaultIndex="innerDefaultIndex"
		@close="close"
		@change="change"
		@confirm="handleConfirm"
	>
		<template #toolbar>
			<slot name="toolbar">
				<toolbar
					:show="props.showToolbar"
					:title="props.title"
					:cancelColor="props.cancelColor"
					:confirmColor="props.confirmColor"
					:cancelText="props.cancelText"
					:confirmText="props.confirmText"
					@cancel="cancel"
					@confirm="confirm"
				></toolbar>
			</slot>
		</template>
		<template #footer>
			<slot name="footer"></slot>
		</template>
	</e-picker>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import mpMixin from '../../libs/mixin/mpMixin';
/**
 * DatetimePicker 时间日期选择器
 * @description 此选择器用于时间日期
 * @tutorial https://www.uviewui.com/components/datetimePicker.html
 * @property {Boolean}			show				用于控制选择器的弹出与收起 ( 默认 false )
 * @property {Boolean}			showToolbar			是否显示顶部的操作栏  ( 默认 true )
 * @property {String | Number} 	value				绑定值
 * @property {String | Number}	modelValue			绑定值
 * @property {String}			title				顶部标题
 * @property {String}			mode				展示格式 mode=date为日期选择，mode=time为时间选择，mode=year-month为年月选择，mode=datetime为日期时间选择  ( 默认 ‘datetime )
 * @property {Number}			maxDate				可选的最大时间  默认值为后10年
 * @property {Number}			minDate				可选的最小时间  默认值为前10年
 * @property {Number}			minHour				可选的最小小时，仅mode=time有效   ( 默认 0 )
 * @property {Number}			maxHour				可选的最大小时，仅mode=time有效	  ( 默认 23 )
 * @property {Number}			minMinute			可选的最小分钟，仅mode=time有效	  ( 默认 0 )
 * @property {Number}			maxMinute			可选的最大分钟，仅mode=time有效   ( 默认 59 )
 * @property {Function}			filter				选项过滤函数
 * @property {Function}			formatter			选项格式化函数
 * @property {Boolean}			loading				是否显示加载中状态   ( 默认 false )
 * @property {String | Number}	itemHeight			各列中，单个选项的高度   ( 默认 44 )
 * @property {String}			cancelText			取消按钮的文字  ( 默认 '取消' )
 * @property {String}			confirmText			确认按钮的文字  ( 默认 '确认' )
 * @property {String}			cancelColor			取消按钮的颜色  ( 默认 '#909193' )
 * @property {String}			confirmColor		确认按钮的颜色  ( 默认 '#3c9cff' )
 * @property {String | Number}	visibleItemCount	每列中可见选项的数量  ( 默认 5 )
 * @property {Boolean}			closeOnClickOverlay	是否允许点击遮罩关闭选择器  ( 默认 false )
 * @event {Function} close 关闭选择器时触发
 * @event {Function} confirm 点击确定按钮，返回当前选择的值
 * @event {Function} change 当选择值变化时触发
 * @event {Function} cancel 点击取消按钮
 * @example  <e-datetime-picker :show="show" :value="value1"  mode="datetime" ></e-datetime-picker>
 */
export default defineComponent({
	name: 'e-datetime-picker',
	mixins: [mpMixin]
});
</script>

<script setup lang="ts">
import { ref, watch, computed, onMounted, toRefs } from 'vue';
import { dateTimePickerProps, dateTimePickerEmits } from './props';
import { padZero, numberRandom, isFunction } from '../../libs/utils';
import dayjs from '../../libs/utils/dayjs';
import { useDefaultProps } from '../../libs/hooks';
import { throwError } from '../../libs/utils/_utils';
import componentsProps, { type DefaultProps } from '../../libs/config/componentsProps';
import toolbar from '../toolbar/index.vue';
import type {
	PickerInstance
	} from '@/uni_modules/endless-ui/typing'

const { datetimePicker: datetimePickerDefaultProps } = toRefs(componentsProps);

const _props = defineProps(dateTimePickerProps);
const props = useDefaultProps<DefaultProps['datetimePicker']>(_props, datetimePickerDefaultProps);

const emit = defineEmits(dateTimePickerEmits);

const modelValue = computed(() => props.value.modelValue || props.value.value);

let innerValue: string | number = '';

type Iteratee = (type: string, value: string) => string;

function times(n: number, iteratee: (i: number) => string) {
	let index = -1;
	const result: string[] = Array(n < 0 ? 0 : n);
	while (++index < n) {
		result[index] = iteratee(index);
	}
	return result;
}
let innerFormatter: Iteratee = (type, value) => value;
// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用
const setFormatter = (e: Iteratee) => {
	innerFormatter = e;
};

const columns = ref<Array<string[]>>([]);
const pickerRef = ref<PickerInstance>();

const innerDefaultIndex = ref<Array<number>>([]);

// 如果以下这些变量发生了变化，意味着需要重新初始化各列的值
const propsChange = computed(() => [
	props.value.mode,
	props.value.maxDate,
	props.value.minDate,
	props.value.minHour,
	props.value.maxHour,
	props.value.minMinute,
	props.value.maxMinute,
	props.value.filter
]);

const isDate = (value: string | number | Date) => {
	if (!value) return false;
	// 判断是否数值或者字符串数值(意味着为时间戳)，转为数值，否则new Date无法识别字符串时间戳
	if (/^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(`${value}`)) value = +value;
	return !/Invalid|NaN/.test(new Date(value).toString());
};
// 得出合法的时间
const correctValue = (value: number | string | Date) => {
	const isDateMode = props.value.mode !== 'time';
	if (isDateMode && !isDate(value)) {
		// 如果是日期类型，但是又没有设置合法的当前时间的话，使用最小时间为当前时间
		value = props.value.minDate as number;
	} else if (!isDateMode && !value) {
		// 如果是时间类型，而又没有默认值的话，就用最小时间
		value = `${padZero(props.value.minHour as number)}:${padZero(props.value.minMinute as number)}`;
	}
	// 时间类型
	if (!isDateMode) {
		if (String(value).indexOf(':') === -1) throwError('e-datetime-picker', '时间错误，请传递如12:24的格式');
		let [hour, minute] = (value as string).split(':');
		// 对时间补零，同时控制在最小值和最大值之间
		hour = padZero(numberRandom(props.value.minHour, props.value.maxHour, Number(hour)));
		minute = padZero(numberRandom(props.value.minMinute, props.value.maxMinute, Number(minute)));
		return `${hour}:${minute}`;
	} else {
		// 如果是日期格式，控制在最小日期和最大日期之间
		value = dayjs(value).isBefore(dayjs(props.value.minDate)) ? (props.value.minDate as number) : value;
		value = dayjs(value).isAfter(dayjs(props.value.maxDate)) ? (props.value.maxDate as number) : value;
		return value as number;
	}
};

//用正则截取输出值,当出现多组数字时,抛出错误
const intercept = (e: string, type?: string) => {
	let judge = e.match(/\d+/g);
	if (!judge) return '0';
	//判断是否掺杂数字
	if (judge.length > 1) {
		throwError('e-datetime-picker', '请勿在过滤或格式化函数时添加数字');
	} else if (type && judge[0].length == 4) {
		//判断是否是年份
		return judge[0];
	} else if (judge[0].length > 2) {
		throwError('e-datetime-picker', '请勿在过滤或格式化函数时添加数字');
	} else {
		return judge[0];
	}
};
type ConfirmEvent = {
	indexs: Array<number>;
	values: [Array<string>, Array<string>, Array<string>, Array<string>, Array<string>];
};
const getNewValue = (e: ConfirmEvent) => {
	const { indexs, values } = e;
	let selectValue: string | number = '';
	if (props.value.mode === 'time') {
		// 根据value各列索引，从各列数组中，取出当前时间的选中值
		selectValue = `${intercept(values[0][indexs[0]])}:${intercept(values[1][indexs[1]])}`;
	} else {
		// 将选择的值转为数值，比如'03'转为数值的3，'2019'转为数值的2019
		const year = parseInt(intercept(values[0][indexs[0]], 'year'));
		const month = parseInt(intercept(values[1][indexs[1]]));
		let date = parseInt(values[2] ? intercept(values[2][indexs[2]]) : '1');
		let hour = 0,
			minute = 0;
		// 此月份的最大天数
		const maxDate = dayjs(`${year}-${month}`).daysInMonth();
		// year-month模式下，date不会出现在列中，设置为1，为了符合后边需要减1的需求
		if (props.value.mode === 'year-month') {
			date = 1;
		}
		// 不允许超过maxDate值
		date = Math.min(maxDate, date);
		if (props.value.mode === 'datetime') {
			hour = parseInt(intercept(values[3][indexs[3]]));
			minute = parseInt(intercept(values[4][indexs[4]]));
		}
		// 转为时间模式
		selectValue = Number(new Date(year, month - 1, date, hour, minute));
	}
	// 取出准确的合法值，防止超越边界的情况
	selectValue = correctValue(selectValue);

	return selectValue;
};

// 根据minDate、maxDate、minHour、maxHour等边界值，判断各列的开始和结束边界值
const getBoundary = (type: 'min' | 'max', innerValue: string | number) => {
	const value = new Date(innerValue);
	const boundary = new Date(props.value[`${type}Date`] as number);
	const year = dayjs(boundary).year();
	let month = 1;
	let date = 1;
	let hour = 0;
	let minute = 0;
	if (type === 'max') {
		month = 12;
		// 月份的天数
		date = dayjs(value).daysInMonth();
		hour = 23;
		minute = 59;
	}
	// 获取边界值，逻辑是：当年达到了边界值(最大或最小年)，就检查月允许的最大和最小值，以此类推
	if (dayjs(value).year() === year) {
		month = dayjs(boundary).month() + 1;
		if (dayjs(value).month() + 1 === month) {
			date = dayjs(boundary).date();
			if (dayjs(value).date() === date) {
				hour = dayjs(boundary).hour();
				if (dayjs(value).hour() === hour) {
					minute = dayjs(boundary).minute();
				}
			}
		}
	}
	return {
		[`${type}Year`]: year,
		[`${type}Month`]: month,
		[`${type}Date`]: date,
		[`${type}Hour`]: hour,
		[`${type}Minute`]: minute
	};
};

// 获取每列的最大和最小值
const getRanges: () => {
	type: string;
	range: number[];
}[] = () => {
	if (props.value.mode === 'time') {
		return [
			{
				type: 'hour',
				range: [props.value.minHour as number, props.value.maxHour as number]
			},
			{
				type: 'minute',
				range: [props.value.minMinute as number, props.value.maxMinute as number]
			}
		];
	}
	const { maxYear, maxDate, maxMonth, maxHour, maxMinute } = getBoundary('max', innerValue);
	const { minYear, minDate, minMonth, minHour, minMinute } = getBoundary('min', innerValue);
	const result = [
		{
			type: 'year',
			range: [minYear, maxYear]
		},
		{
			type: 'month',
			range: [minMonth, maxMonth]
		},
		{
			type: 'day',
			range: [minDate, maxDate]
		},
		{
			type: 'hour',
			range: [minHour, maxHour]
		},
		{
			type: 'minute',
			range: [minMinute, maxMinute]
		}
	];
	if (props.value.mode === 'date') result.splice(3, 2);
	if (props.value.mode === 'year-month') result.splice(2, 3);
	return result;
};

const getOriginColumns = () => {
	// 生成各列的值
	const results = getRanges().map(({ type, range }) => {
		let values = times(range[1] - range[0] + 1, (index) => {
			let value = range[0] + index;
			return type === 'year' ? `${value}` : padZero(value);
		});

		// 进行过滤
		if (isFunction(props.value.filter)) {
			let fn = props.value.filter;
			values = values.map((item) => fn(type, item));
		}
		return {
			type,
			values
		};
	});
	return results;
};
// 更新各列的值
const updateColumns = () => {
	const formatter = (props.value.formatter as Iteratee) || innerFormatter;
	// 获取各列的值，并且map后，对各列的具体值进行补0操作
	const results = getOriginColumns().map((column) => column.values.map((value) => formatter(column.type, value)));
	columns.value = results;
};
// 更新索引
const updateIndexs = (value: string | number) => {
	let values: string[] = [];
	const formatter = props.value.formatter || innerFormatter;
	if (props.value.mode === 'time') {
		// 将time模式的时间用:分隔成数组
		const timeArr = (value as string).split(':');
		// 使用formatter格式化方法进行管道处理
		values = [formatter('hour', timeArr[0]), formatter('minute', timeArr[1])];
	} else {
		values = [
			formatter('year', `${dayjs(value).year()}`),
			// 月份补0
			formatter('month', padZero(dayjs(value).month() + 1))
		];
		if (props.value.mode === 'date') {
			// date模式，需要添加天列
			values.push(formatter('day', padZero(dayjs(value).date())));
		}
		if (props.value.mode === 'datetime') {
			// 数组的push方法，可以写入多个参数
			values.push(formatter('day', padZero(dayjs(value).date())), formatter('hour', padZero(dayjs(value).hour())), formatter('minute', padZero(dayjs(value).minute())));
		}
	}
	// 根据当前各列的所有值，从各列默认值中找到默认值在各列中的索引
	const indexs = columns.value.map((column, index) => {
		// 通过取大值，可以保证不会出现找不到索引的-1情况
		return Math.max(
			0,
			column.findIndex((item) => item === values[index])
		);
	});

	innerDefaultIndex.value = indexs;
};

// 更新各列的值，进行补0、格式化等操作
const updateColumnValue = (value: string | number) => {
	innerValue = value;
	updateColumns();
	updateIndexs(value);
};

// 列发生变化时触发
const change = (e: ConfirmEvent) => {
	let selectValue = getNewValue(e);
	innerValue = selectValue;
	updateColumnValue(selectValue);
	// 发出change时间，value为当前选中的时间戳
	emit('change', {
		value: selectValue,
		// #ifndef MP-WEIXIN
		// 微信小程序不能传递this实例，会因为循环引用而报错
		// picker: this.$refs.picker,
		// #endif
		mode: props.value.mode as string
	});
};

// 关闭选择器
const close = () => {
	if (props.value.closeOnClickOverlay) {
		emit('close');
	}
};
// 点击工具栏的取消按钮
const cancel = () => {
	emit('cancel');
};

const confirm = () => {
	pickerRef.value?.confirm?.();
};

// 点击工具栏的确定按钮
const handleConfirm = (e: ConfirmEvent) => {
	innerValue = getNewValue(e);
	emit('confirm', {
		value: innerValue,
		mode: props.value.mode as string
	});
	emit('update:modelValue', innerValue);
};

const init = () => {
	innerValue = correctValue(modelValue.value||new Date());
	updateColumnValue(innerValue);
};

watch(
	() => modelValue.value,
	(v) => {
		innerValue = correctValue(v);
	}
);

watch(
	() => props.value.show,
	(newValue) => {
		if (newValue) {
			updateColumnValue(innerValue);
		}
	}
);
watch(
	() => propsChange.value,
	(newValue, oldValue) => {
		init();
		// if (newValue.toString() !== oldValue.toString()) init(true)
	}
);

onMounted(() => {
	init();
});

defineExpose({
	setFormatter,
	confirm
});
</script>

<style lang="scss" scoped>
@import '../../libs/theme/datetime-picker.scss';
</style>
